using Engine.Graphics;
using Engine.MeshObject;
using Scenario_Editor.Tool;
using System;
using System.Collections.Generic;
using System.Drawing;

namespace Scenario_Editor
{
    internal class TerrainSector
    {
        #region Public Fields

        public BoundingBox boundingBox;
        public float highestX;
        public float highestY;
        public float highestZ;
        public float lowestX;
        public float lowestY;
        public float lowestZ;
        public int[] subsetMaterials;
        public int[] subsetnIndices;
        public Meshobject temporaryterrainMesh;
        public Meshobject terrainMesh;

        #endregion Public Fields

        #region Private Fields

        private List<Point> cellsWhichMustUpdate = new List<Point>();

        private int coordinateX;

        private int coordinateY;

        /// <summary>
        /// The Meshes which are registered in this list which will added to the terrainMesh.
        /// </summary>
        private Dictionary<Meshobject, List<MeshPolygonData>> registeredMeshes;

        #endregion Private Fields

        #region Internal Constructors

        internal TerrainSector(int coordinateX, int coordinateY, Engine.Engine engine, MaterialManager materialManager, ShaderManager shaderManager)
            : this()
        {
            this.coordinateX = coordinateX;
            this.coordinateY = coordinateY;
            this.terrainMesh = new Meshobject(engine.Device, materialManager, shaderManager);
            this.boundingBox = new BoundingBox(engine);
        }

        #endregion Internal Constructors

        #region Private Constructors

        private TerrainSector()
        {
            this.subsetMaterials = new int[0];
            this.subsetnIndices = new int[0];
            this.coordinateX = -1;
            this.coordinateY = -1;
            this.registeredMeshes = new Dictionary<Meshobject, List<MeshPolygonData>>();
            this.cellsWhichMustUpdate = new List<Point>(256);
            this.TracedShadowSectors = new List<Point>();
            this.ReceivedShadowFromSector = new List<Point>();
        }

        #endregion Private Constructors

        #region Private Destructors

        ~TerrainSector()
        {
            this.Dispose();
            this.boundingBox = null;
            this.registeredMeshes = null;
            this.subsetMaterials = null;
            this.subsetnIndices = null;
            this.terrainMesh = null;
        }

        #endregion Private Destructors

        #region Public Properties

        public bool MustUpdated { get; set; }

        public bool UpdateVertexBuffer { get; set; }

        #endregion Public Properties

        #region Internal Properties

        internal Dictionary<Meshobject, List<MeshPolygonData>> RegisteredMeshes
        {
            get { return registeredMeshes; }
            set { registeredMeshes = value; }
        }

        #endregion Internal Properties

        #region Public Methods

        /// <summary>
        /// Adds a cell-coordinate to the list where the dirty cell coordinates are stored in.
        /// </summary>
        /// <param name="cellCoordinate"></param>
        public void AddCoordinateWhichMustUpdate(Point cellCoordinate)
        {
            lock (this.cellsWhichMustUpdate)
            {
                if (!this.cellsWhichMustUpdate.Contains(cellCoordinate))
                    this.cellsWhichMustUpdate.Add(cellCoordinate);
            }
        }

        /// <summary>
        /// Dispose the Terrainsector.
        /// </summary>
        public void Dispose()
        {
            //this.registeredMeshes.Clear();
            if (this.terrainMesh != null)
                this.terrainMesh.DisposeBuffers();
            if (this.boundingBox != null)
                this.boundingBox.Dispose();
            this.boundingBox = null;
        }

        public override string ToString()
        {
            return this.MustUpdated.ToString();
        }

        #endregion Public Methods

        #region Internal Methods

        internal void AddPolygonId(Meshobject mesh, int material, int currentPolygon)
        {
            List<MeshPolygonData> meshPolygonData;
            lock (this.RegisteredMeshes)
            {
                if (this.RegisteredMeshes.TryGetValue(mesh, out meshPolygonData))
                {
                    bool materialFound = false;
                    foreach (MeshPolygonData item in meshPolygonData)
                    {
                        if (item.MaterialId == material)
                        {
                            materialFound = true;
                            if (!item.PolygonIds.Contains(currentPolygon))
                                item.PolygonIds.Add(currentPolygon);
                        }
                    }
                    if (!materialFound)
                    {
                        meshPolygonData.Add(new MeshPolygonData(material, new List<int>() { currentPolygon }));
                    }
                }
                else // Add a new entry.
                {
                    meshPolygonData = new List<MeshPolygonData>();
                    meshPolygonData.Add(new MeshPolygonData(material, new List<int>() { currentPolygon }));
                    this.RegisteredMeshes.Add(mesh, meshPolygonData);
                }
            }
        }

        internal void ClearCellsWhichMustUpdate()
        {
            lock (this.cellsWhichMustUpdate)
            {
                this.cellsWhichMustUpdate.Clear();
            }
        }

        internal List<Point> GetCellsWhichMustUpdate()
        {
            lock (this.cellsWhichMustUpdate)
            {
                return new List<Point>(this.cellsWhichMustUpdate);
            }
        }

        internal void RemoveCellWhichMustUpdate(Point point)
        {
            lock (this.cellsWhichMustUpdate)
            {
                if (!this.cellsWhichMustUpdate.Remove(point))
                    throw new Exception("Point not in list!");
            }
        }

        internal void UnRegisterMesh(Meshobject mesh)
        {
            lock (this.RegisteredMeshes)
            {
                if (this.RegisteredMeshes.ContainsKey(mesh))
                    this.RegisteredMeshes.Remove(mesh);
            }
        }

        internal void UpdateBoundingBox(float SECTORSIZE, int nSectorsX, int nSectorsY)
        {
            // This is used for calculating bounding box.
            this.highestX = (-SECTORSIZE / 2 * nSectorsX) + (this.coordinateX * SECTORSIZE) + (SECTORSIZE);
            this.lowestX = (-SECTORSIZE / 2 * nSectorsX) + (this.coordinateX * SECTORSIZE) + (0 * SECTORSIZE);
            this.highestY = float.MinValue;
            this.lowestY = float.MaxValue;
            this.highestZ = (-SECTORSIZE / 2 * nSectorsY) + (this.coordinateY * SECTORSIZE) + (SECTORSIZE);
            this.lowestZ = (-SECTORSIZE / 2 * nSectorsY) + (this.coordinateY * SECTORSIZE) + (0 * SECTORSIZE);

            for (int i = 0; i < this.terrainMesh.Vertices.Length; i++)
            {
                this.highestY = Math.Max(this.highestY, this.terrainMesh.Vertices[i].Y);
                this.lowestY = Math.Min(this.lowestY, this.terrainMesh.Vertices[i].Y);
            }
            boundingBox.UpdateBox(this.highestX, this.lowestX, this.highestY, this.lowestY, this.highestZ, this.lowestZ);
        }

        #endregion Internal Methods

        public List<Point> ReceivedShadowFromSector { get; set; }
        public List<Point> TracedShadowSectors { get; set; }
    }
}